#include "core.h"
#include "namespace.h"


/** @brief Prefix / Namespace pair.
 */
typedef struct ns_entry_t {
    ns_pfx              pfx;        // Namespace prefix.
    char *              ns;         // Fully qualified NS.
} NSEntry;

typedef struct hashmap NSMap;

/** @brief Iterator for dumping NS map.
 */
struct dump_iter_t {
    size_t              i;          // Iterator counter.
    const char ***      data;       // Stored data.
};


/**
 * Callbacks.
 */
static int nsmap_comp_fn (const void *a, const void *b, void *udata)
{
    const NSEntry *nsa = a;
    const NSEntry *nsb = b;

    return strncmp (nsa->pfx, nsb->pfx, PFX_LEN);
}

static uint64_t nsmap_hash_fn (
        const void *item, uint64_t seed0, uint64_t seed1)
{
    const NSEntry *nse = item;
    return (uint64_t) LSUP_HASH64 (nse->pfx, PFX_LEN, seed0);
}

static void nsmap_free_fn (void *item)
{ free (((NSEntry *) item)->ns); }


static bool nsmap_dump_ns_iter_fn (const void *item, void *udata)
{
    const NSEntry *entry = item;
    struct dump_iter_t *cur = udata;

    cur->data[cur->i][0] = (const char *)entry->pfx;
    cur->data[cur->i++][1] = (const char *)entry->ns;

    return true;
}

/**
 * API.
 */

NSMap *
LSUP_nsmap_new (void)
{
    return hashmap_new (
            sizeof (NSEntry), 0, LSUP_HASH_SEED, 0,
            nsmap_hash_fn, nsmap_comp_fn, nsmap_free_fn, NULL);
}


void
LSUP_nsmap_free (NSMap *map)
{ hashmap_free (map); }


LSUP_rc
LSUP_nsmap_add (NSMap *map, const char *pfx, const char *nsstr)
{
    NSEntry entry_s;

    if (strlen(pfx) >= PFX_LEN)
        log_warn(
                "Prefix `%s` is longer than the maximum allowed size "
                "(%d characters). Truncating.", pfx, PFX_LEN - 1);
    strncpy (entry_s.pfx, pfx, sizeof (entry_s.pfx));
    char *ns = strdup (nsstr);
    entry_s.ns = ns;

    NSEntry *ret = hashmap_set (map, &entry_s);
    if (hashmap_oom (map)) return LSUP_MEM_ERR;
    // Free replaced NS string.
    if (ret) {
        free (ret->ns);
        free (ret);
    }

    return LSUP_OK;
}


LSUP_rc
LSUP_nsmap_remove (NSMap *map, const char *pfx)
{
    NSEntry entry_s;
    strncpy (entry_s.pfx, pfx, PFX_LEN);
    NSEntry *entry = hashmap_delete (map, &entry_s);

    if (!entry) return LSUP_NOACTION;

    free (entry->ns);

    return LSUP_OK;
}


const char *
LSUP_nsmap_get_ns (NSMap *map, const char *pfx)
{
    NSEntry entry_s;
    strncpy (entry_s.pfx, pfx, PFX_LEN);
    NSEntry *entry = hashmap_get (map, &entry_s);

    return (entry) ? entry->ns : NULL;
}


const char *
LSUP_nsmap_get_pfx (NSMap *map, const char *ns)
{
    const NSEntry *entry;
    size_t i = 0;
    while (hashmap_iter (map, &i, (void **) &entry)) {
        if (strncmp (entry->ns, ns, strlen (ns)) == 0)
            return entry->pfx;
    }

    return NULL;
}


LSUP_rc
LSUP_nsmap_normalize_uri (
        NSMap *map, const char *pfx_uri, char **fq_uri_p)
{
    char *fq_uri = NULL;

    size_t pfx_len = strcspn (pfx_uri, ":");
    if (pfx_len >= PFX_LEN) {
        log_warn(
                "Prefix in `%s` is longer than the maximum allowed size "
                "(%d characters). Truncating.", pfx_uri, PFX_LEN - 1);
        pfx_len = PFX_LEN - 1;
    }

    ns_pfx pfx;
    strncpy (pfx, pfx_uri, pfx_len);
    pfx[pfx_len] = 0;

    /*
    Namespace *entry;
    for (entry = map; entry != NULL; entry = entry->hh.next) {
        if (strncmp (entry->pfx, pfx_uri, strlen (entry->pfx)) == 0)
            break;
    }
    */
    const char *ns = LSUP_nsmap_get_ns (map, pfx);

    if (ns) {
        // -1 for :, +1 for terminator.
        size_t fq_size = strlen (ns) + strlen (pfx_uri) - pfx_len;
        fq_uri = malloc (fq_size);
        if (UNLIKELY (! (fq_uri))) return LSUP_MEM_ERR;

        strcpy (fq_uri, ns);
        strcat (fq_uri, pfx_uri + pfx_len + 1);
    }

    else fq_uri = strdup (pfx_uri);

    *fq_uri_p = fq_uri;

    return LSUP_OK;
}


LSUP_rc
LSUP_nsmap_denormalize_uri (
        NSMap *map, const char *fq_uri, char **pfx_uri_p)
{
    /*
     * This is different from LSUP_nsmap_get_ns, in that the URI being looked
     * at will unlikely match exactly the full namespace stored in the map.
     * This function has to count the characters left over from the match in
     * order to add the URI suffix.
     */
    const NSEntry *entry;
    const char *pfx = NULL;
    size_t i = 0, offset;
    while (hashmap_iter (map, &i, (void **) &entry)) {
        offset = strlen(entry->ns);
        if (strncmp (entry->ns, fq_uri, offset) == 0) {
            pfx = entry->pfx;
            break;
        }
    }
    char *pfx_uri = NULL;

    if (pfx) {
        // +2: one for terminating \x00, one for the colon.
        pfx_uri = malloc (strlen (pfx) + strlen (fq_uri) - offset + 2);
        if (UNLIKELY (! (pfx_uri))) return LSUP_MEM_ERR;

        sprintf (pfx_uri, "%s:%s", pfx, fq_uri + offset);

    }

    else pfx_uri = strdup (fq_uri);

    *pfx_uri_p = pfx_uri;

    return LSUP_OK;
}


const char ***
LSUP_nsmap_dump (const NSMap *map)
{
    size_t i = hashmap_count ((NSMap *) map);

    const char ***data = malloc (2 * (i + 1) * sizeof (char *));
    if (UNLIKELY (!data)) return NULL;

    for (size_t j = 0; j < i; j++) {
        data[j] = malloc (2 * sizeof (char *));
        if (UNLIKELY (!data[j])) return NULL;
    }

    struct dump_iter_t cur = {.i = 0, .data = data};
    hashmap_scan ((NSMap *) map, nsmap_dump_ns_iter_fn, &cur);
    data[i] = NULL; // Sentinel

    return data;
}
